Minería de Datos

Hito 3 Dinamicidad de Consultas en la Web de Datos

Equipo 4: Alberto Moya Loustaunau, Grethel Coello Said, Jesús Pérez Marín

Introducción

Por su naturaleza, una gran cantidad de contenido encontrado en la web es dinámico. Se pueden encontrar conjuntos de datos publicados como datos abiertos linkeados (LOD, por sus siglas en inglés (Linked Open Data)) en la web. La nube LOD contiene varios miles de millones de descripciones de recursos en formato RDF (Resource Description Framework), información legible por máquinas, que describen cosas (recursos) del mundo real y relaciones entre ellas. Estos datos forman un enorme grafo dirigido y etiquetado (los recursos son nodos, las relaciones son aristas), a los que los humanos y agentes de software inteligentes pueden acceder. Al mismo tiempo los datos y enlaces entre los datos son enriquecidos constantemente por humanos y máquinas (por ejemplo, convertidores de datos o sistemas de administración de contenido).

Varios autores han abordado la dinámica de la web en general, sin embargo los enfoques existentes de la web tradicional no son suficientes para explorar y descubrir las dinámicas en la Web de Datos, ya que no tienen en cuenta las características especiales de LOD. El entendimiento de la dinámica en LOD, entre otras cosas podría ayudar a decidir qué consultas o partes de esta pueden ejecutarse sobre una memoria caché, y cuales en vivo a través del contenido web para garantizar resultados actualizados a menor costo. Además existen diferentes temas relacionados con la administración de datos web que pueden beneficiarse de una comprensión más profunda de la dinámica de los conjuntos de datos, como son: rastreo y almacenamiento en la web [Cho y Garcia-Molina, 2003], mantenimiento de la integridad de enlaces [Haslhofer y Popitsch, 2009] o el servicio de consultas continuas [Pandey et al., 2003].

En la actualidad es creciente el número de aplicaciones que consultan información de LOD para ofrecer variados servicios. La forma estándar de consultar este tipo de datos es mediante el lenguaje de consultas SPARQL. En este contexto, nos proponemos analizar la dinamicidad de los datos en la web a través del estudio de consultas en lenguaje SPARQL e intentaremos responder las siguientes preguntas:

  1. ¿Qué clases de dinamicidad podemos distinguir en las consultas?
  2. ¿Qué métodos reales son adecuados para clasificar las consultas con respecto a la dinámica de sus resultados?
  3. ¿En qué características deberían basarse estos métodos?
  4. ¿Cómo podemos predecir de manera eficiente cuándo cambiarán los resultados de una consulta?

Datos

Con el objetivo de abordar el problema de estimar cambios futuros en los resultados de las consultas y tomando como hipótesis que, la dinamicidad de los resultados de una consulta se puede predecir a partir de la propia consulta y estadísticas de la dinamicidad de los datos, emplearemos 23 versiones del grafo de conocimiento de Wikidata y una colección de aproximadamente 400 consultas extraídas de la página de Wikidata. Seleccionamos Wikidata porque proporciona un historial de versiones semanales que podemos usar para evaluar predicciones y está editado por miles de usuarios colaborativamente, lo que significa que se observan cambios significativos semanalmente.

Dataset RDF

Utilizaremos 23 versiones de Wikidata en el período del 18/04/2017 al 27/09/2017, que se capturaron semanalmente. Trabajamos con la versión de truthy en el formato RDF N-triples, que representa declaraciones que tienen el mejor rango no en desuso para una propiedad determinada. En relación a las versiones se puede decir que:

  • la primera versión tiene 1,102,242,331 triples, 57,499,191 IRI únicos, 3,276 predicados únicos, 933,588,860 literales y 7,449 nodos en blanco,
  • mientras que la versión final tiene 1,924,967,162 triples (+ 74%), 80,637,164 IRI únicos (+ 40%), 3,660 predicados únicos (+ 11%), 1,676,930,476 literales (+ 79%) y 8,909 nodos en blanco (+ 19%).

En la Figura 1 se muestra el crecimiento mantenido durante el período de algunos de estos indicadores. El salto en la semana 12 se debe al hecho de que no fue posible obtener los datos para esa semana. De manera general se puede observar que la información es relativamente incremental, pero existen eliminaciones. La Figura 2 muestra la proporción de triples eliminados y agregados en cada semana.

Dataset de consultas

Una vez que se tiene el conjunto de datos dinámico, con dominios cruzados y con información histórica sobre su evolución, es necesario tener una colección de consultas para el conjunto de datos. Al momento de comenzar con el trabajo, no se encontró un conjunto de datos estructurado con consultas siguiendo el modelo de datos de Wikidata, sino que se encontró un conjunto de 388 consultas de ejemplos sobre Wikidata, distribuidas en 18 categorías, que incluyen: simple, showcase, Wikibase predicates, Wikimedia projects, entertainment, geography, demography, politics, economics, and business, science, history, culture, food & drink, sports, women, bibliographic citation (Wikicite), maintenance, europeana, and unsorted. Estas consultas serán consideradas para evaluar nuestra hipótesis. texto alternativo

Figura 1: A la izquierda, evolución del número de triples en Wikidata. A la derecha: triples añadidos y eliminados en Wikidata.

Las consultas requerían ser procesadas manualmente, antes de realizar un análisis sobre ellas, porque tenían errores de sintaxis; y otras no fueron consideradas, porque solicitaron información externa a Wikidata, como DBpedia y bigdata. Consideramos que los resultados de la consulta han cambiado si no son los mismos resultados que se obtuvieron para esta consulta en la evaluación anterior. Por lo tanto, se necesitan consultas deterministas, es decir, eliminar las que podrían cambiar su resultado incluso cuando los datos son los mismos. En nuestro caso, se eliminaron las consultas con la palabra clave SAMPLE y agregamos ORDER BY a todas las consultas con todas las variables proyectadas. Este último cambio facilita la detección de cambios en los resultados al iterar sobre ambos conjuntos de resultados ordenados. Al finalizar el procesamiento sobre las consultas, se obtiene un total de 221 consultas. Sobre estas se realizó un análisis del uso básico de las características SPARQL contando las palabras clave en las consultas (ver Tabla 1).

Tabla 1: A la izquierda, frecuencias de Keywords en las consultas. En el centro, Patrones triples (C = Constante, V = Variable). A la derecha, la frecuencia de uso de las reglas en los Patrones de triples.

texto alternativo

En la primera parte de la Tabla 1 (a la izquierda) se describe el tipo de consultas y se puede apreciar que solo tiene consultas SELECT. La segunda parte contiene modificadores de solución, y se observa que todas (100%) las consultas utilizan ORDER BY, lo cual se debe al proceso de determinación utilizado, LIMIT se usa en 18.10% y DISTINCT en 29.41%. La tercera parte tiene palabras clave asociadas con los operadores del álgebra SPARQL, en este sentido, FILTER aparece en un 92.31% y OPTIONAL en 82.35% lo que significa que son bastante comunes, debido al proceso de eliminación del servicio de idiomas utilizado y, en menor medida, UNION en un 6.79% y NOT EXISTS en un 5.43%, MINUS 1.81 % y EXISTS 0,45%. La cuarta parte tiene operadores de agregación, siendo el más utilizado GROUP BY en un 23.08%, el cual se combina con la mayoría de los operadores de agregación, dentro de los operadores, se destaca el uso de COUNT en un 19,91% y el resto se usa escasamente, en menos del 1,81%. Una vez mostrado el análisis básico sobre la presencia de palabras clave en las consultas, se analizará la estructura de los patrones triples. Esto es importante, porque nuestra noción de frescura se centra en los predicados. Esto restringe nuestro enfoque a patrones triples con una constante en la posición de predicado. Por lo tanto, nuestro primer objetivo es verificar la frecuencia de dichos patrones en las consultas. En la Tabla 1 (en el centro), se muestra que el patrón VCV (71.73%) es el más usado, VCC (18.05%) también es muy común y en total el 90.35% de los patrones triples tiene una constante en la posición de predicado, lo que ayuda a validar nuestra propuesta de usar un modelo basado en la dinámica de los predicados. Se evaluó la dinámica de los resultados de las consultas entre los resultados de la misma consulta en 23 versiones consecutivas de Wikidata. Se pudo identificar que aproximadamente la mitad de los resultados de las consultas cambian cada semana, como se ilustra en la Figura 2. En la Figura 2 se muestra para cada consulta, cuántos cambios se registraron durante 23 semanas. Existen consultas que cambian cada semana, otras que nunca cambiaron y una distribución bastante uniforme de los cambios en el período. En la Figura 3 se ilustra un análisis de la monotonicidad del resultado de la consulta en el período evaluado. Los puntos rojos significan solo resultados eliminados, los verdes agregados y los azules agregados y eliminados. Se puede observar que los resultados de la consulta no son monótonos.

texto alternativo Figura 2: A la izquierda, cambios en los resultados de las consultas por tiempo. A la izquierda, cambios en los resultados de las consultas por consulta.

In [0]:
import pandas as pd
import io

#load csv
#df = pd.read_csv('https://users.dcc.uchile.cl/~amoya/classdm1b.csv', sep=',')
#df = pd.read_csv('https://users.dcc.uchile.cl/~amoya/classdm1.csv', sep=',')
df = pd.read_csv('https://users.dcc.uchile.cl/~amoya/classdmb.csv', sep=',')
#df = pd.read_csv('https://users.dcc.uchile.cl/~amoya/classdm.csv', sep=',')
#df = pd.read_csv('https://users.dcc.uchile.cl/~amoya/class.csv', sep=',')
#df = pd.read_csv('https://users.dcc.uchile.cl/~amoya/ttl.csv', sep=',')

#En el dataset existen algunos atributos que no aportan a nuestros objetivos 
#o representan redundancia con otros atributos.
#lista de atributos a analizar
features_to_plot = [e for e in list(df) if e not in 
                    ['query_id', 'dim_min', 'dim_max', 'changes_count']]

df[features_to_plot].describe()
Out[0]:
triples_count vars_count has_filter has_limit has_union has_subquery has_asterpath has_neg projected_vars_count predicates_count dim_avg dim_class
count 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000 221.000000
mean 4.737557 4.479638 0.923077 0.180995 0.067873 0.095023 0.266968 0.099548 3.515837 3.855204 0.387495 0.429864
std 3.190026 2.848571 0.267074 0.385888 0.252099 0.293912 0.443380 0.300075 1.650337 1.741690 0.453293 0.496180
min 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 1.000000 0.000008 0.000000
25% 3.000000 3.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 2.000000 3.000000 0.216545 0.000000
50% 4.000000 4.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 3.000000 3.000000 0.286509 0.000000
75% 6.000000 5.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 4.000000 5.000000 0.382482 1.000000
max 30.000000 30.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 16.000000 10.000000 4.393085 1.000000

A continuación mostramos la matriz de correlación entre los atributos de nuestro dataset. Los valores cercanos a 1 y -1 implican alta correlación, mientras que los valores cercanos a 0 indican una baja correlación

In [0]:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set(style="ticks", color_codes=True)

corr = df[features_to_plot].corr()

f, ax = plt.subplots(figsize=(10, 8)) 
sns.heatmap(corr, mask=np.zeros_like(corr, dtype=np.bool), cmap=sns.diverging_palette(220, 10, as_cmap=True), square=True, ax=ax) 
Out[0]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f3195d59ba8>

Continuando con el análisis de la correlación entre los atributos y cuanto aportan a la correcta clasificación de nuestras nuestros datos, ploteamos su comportameinto por pares en relación a cada una de las clases de nuestro etiquetado.

In [0]:
#g = sns.pairplot(df, kind="reg") # Parametro kind="reg" agrega una recta
g = sns.pairplot(df[features_to_plot], hue="ttl", kind="reg") # Otra visualizacion
plt.show()

A continuación relizamos la normalización de los valores de nuestro dataset.

In [32]:
from sklearn import preprocessing

#remove query_id, dim_min, dim_max, changes_count and dim_class columns
features_names = [e for e in list(df) if e not in ['query_id', 'dim_min', 'dim_max', 'changes_count', 'past', 'dim_class', 'ttl']]

#y = df['dim_class']
#y = df['ttl']

#normalizing
X = df[features_names].values #returns a numpy array
min_max_scaler = preprocessing.MinMaxScaler()
X = min_max_scaler.fit_transform(X)
X
Out[32]:
array([[0.03448276, 0.03448276, 1.        , ..., 0.06666667, 0.11111111,
        0.12603106],
       [0.03448276, 0.03448276, 1.        , ..., 0.06666667, 0.11111111,
        0.12603106],
       [0.06896552, 0.06896552, 1.        , ..., 0.13333333, 0.22222222,
        0.08477144],
       ...,
       [0.03448276, 0.03448276, 1.        , ..., 0.06666667, 0.11111111,
        0.12603106],
       [0.03448276, 0.03448276, 0.        , ..., 0.06666667, 0.11111111,
        0.12603106],
       [0.06896552, 0.06896552, 1.        , ..., 0.13333333, 0.22222222,
        0.08466036]])

Uno de los datasets con los que probamos nuestros métodos, tiene en cuenta la dinamicidad de los predicados, lo que implica un dataset con un gran número de atributos. En cada fila se tendrían (además de los atributos relacionados a la sintaxis de la consulta) un 0 por cada atributo que no aparece en la consulta y para los que si aparecen, se tiene su dinamicidad. Por tanto, probamos el efecto de reducir la dimencionalidad de este dataset mediante la concervacion de la varianza (algoritmo PCA).

In [0]:
from sklearn.decomposition import PCA

#pca = PCA(n_components=50)
pca = PCA(.95)
X = pca.fit_transform(X)
X.shape

Experimentos y resultados

Para responder las preguntas de investigación realizamos cuatro experimentos. Los dos primeros para evaluar el desempeño de distintos clasificadores para predecir la dinamicidad de consultas teniendo en cuenta tres y dos clases de dinamicidad respectivamente, basadas en la frecuencia de cambio de los resultados.

Clasificación

El tercer experimento se realizó para determinar si es posible predecir el cambio en los resultados de las consultas en una semana usando clasificación. El último experimento fue intentar predecir el tiempo de vida de los resultados deuna consulta expresados en semanas y para lo cual utilizamos regreción lineal múltiple.

En nuestros experimentos se tuvieron en cuenta los clasificadores: Base Dummy, Decision Tree, Gaussian Naive Bayes, KNN y 3 tipos de SVM distintos (SVM-SVC, SVM-NuSVC y SVM-LinearSVC).

Se realizaron 100 corridas con cada clasificador dividiendo el dataset en entrenamiento (70%) y prueba (30%) y se muestran el desempeño promedio en prueba en terminos de las medidas f1-score, recall, precision y accuracy.

A continuación se muestran los resultados alcanzados:

texto alternativo

texto alternativo

texto alternativo

Los resultados obtenidos en los experimentos muestran que de manera general la calidad obtenida en la clasificación por los distintos métodos no fue buena. Teniendo en cuenta esto, continuamos trabajando sobre la hipótesis inicial, pero consideramos que mejorar el etiquetado en la clasificación de clases de dinamicidad pueden mejorar los resultados alcanzados. Hasta el momento, para etiquetar las consultas según su dinamicidad solo se tuvo en cuenta la frecuencia de cambio de la consulta a lo largo de las 22 semanas. Una alternativa a esto, puede ser tener en cuenta nuevos rasgos obtenidos a partir de análisis estadísticos de comportamiento de cambio y su periodicidad; para intentar diferenciar las clases mediante algoritmos de agrupamiento.

Agrupamiento

Un primer paso hacia nuestros objetivos generales es identificar nodos con dinámicas similares. Por lo tanto, primero investigamos cómo agrupar consultas con respecto a características de su dinámica.

Features para el Agrupamiento

Las principales preguntas que debemos responder son: ¿Cuáles son las características que describen la dinámica de un recurso? Las características utilizadas fueron:

Frecuencia de cambio promedio: la dinámica de cada consulta se puede describir simplemente por el número de cambios de resultados de una consulta dividido por el número total de instantáneas en el período monitoreado.

Resúmenes estadísticos del comportamiento del cambio: podríamos utilizar resúmenes estadísticos de cambios en los resultados teniendo en cuenta la magnitud de tales cambios. Como tendencias centrales (media aritmética, mediana o media intercuartil) o dispersión (desviación estándar, varianza o cuantiles).

Periodicidades de los cambios: dichas periodicidades se determinaron mediante transformadas discretas de Fourier, se intentan solucionar los problemas obvios en el uso de frecuencia de cambio promedio (por ejemplo, una consulta cambia muy a menudo al comienzo del tiempo monitoreado pero luego es bastante estática, en comparación con otra que cambia regularmente en intervalos más grandes a lo largo del tiempo).

Etiquetado del dataset

Para etiquetar los datos, tuvimos en cuenta dos ficheros, uno con el historial de cambios por consultas y otro con la magnitud de esos cambios.

In [0]:
import sys
import pandas as pd
data = pd.read_csv('https://users.dcc.uchile.cl/~amoya/cmp.csv', header=None, sep=',').T
data_count = pd.read_csv('https://users.dcc.uchile.cl/~amoya/count.csv', header=None, sep=',').T

data1=data.iloc[1:222,:]
data_count1=data_count.iloc[1:222,:]

Análisis de la periodicidad

In [0]:
data_fft=np.fft.fft(data1)
data_fft_real = data_fft.real
data_fft_real
Out[0]:
array([[ 7.00000000e+00, -4.01763122e-01, -2.26899825e-01, ...,
        -2.02976872e+00, -2.26899825e-01, -4.01763122e-01],
       [ 2.00000000e+00,  5.44077961e-01,  1.86392799e-01, ...,
         1.61435371e+00,  1.86392799e-01,  5.44077961e-01],
       [ 7.00000000e+00,  2.52764633e+00,  3.57685162e-01, ...,
         2.05662386e+00,  3.57685162e-01,  2.52764633e+00],
       ...,
       [ 1.60000000e+01, -3.77389137e+00,  1.25458957e-02, ...,
         8.21855065e-01,  1.25458957e-02, -3.77389137e+00],
       [ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00, ...,
         0.00000000e+00,  0.00000000e+00,  0.00000000e+00],
       [ 2.20000000e+01,  0.00000000e+00, -1.28785871e-14, ...,
         0.00000000e+00, -1.28785871e-14,  0.00000000e+00]])

Analisis estadístico

Promedio de las longitudes de las subsecuencias estáticas y promedio de la frecuencia de cambios.

In [0]:
feats = np.zeros((221,24))
feats[:,:-2] = data_fft_real

c=0
avg_changes=[]
avg_zero_seqs=[]
for row in range(1,222):
  #if not row in outliers:
  sum=0
  zero_seqs=0
  in_seq = False
  for col in range(22):
    sum += data1[col][row]
    if not data1[col][row] and not in_seq:
      zero_seqs += 1
      in_seq = True
    elif data1[col][row]:
      in_seq = False
  avg_changes.append(sum/22)
  avg_zero_seqs.append((22-sum)/zero_seqs if zero_seqs else 0)

feats[:,-2] = avg_changes
feats[:,-1] = avg_zero_seqs

Cuantificación de la magnitud de los cambios

In [0]:
c_avg = [data_count1.iloc[i,:].sum()/22 for i in range(0,219)]
c_std = [data_count1.iloc[i,:].std() for i in range(0,219)]
#feats[:,-2]=c_avg
#feats[:,-1]=c_std

Detección y eliminación de outliers

In [0]:
import matplotlib.pyplot as plt

plt.plot(c_avg, 'ro-', linewidth=2)
# plt.axis([100, 150, -10, 400])
plt.show()
In [0]:
outliers = [126,208]
data1 = data_count1.drop(outliers)
data_count1 = data_count1.drop(outliers)

Agrupamiento con KMeans

Aplicamos kmeans utilizando diferentes subconjuntos de características y evaluando la cohesión y separación de los clusters. A continuación mostramos el agrupamiento que obtuvo mejores índices de calidad.

In [0]:
import matplotlib.pyplot as plt

feats1=np.zeros((221,2))
feats1[:,:] = feats[:,-2:]

plt.plot(feats1[:,0], feats1[:,1], 'ro')
plt.show()
In [0]:
from sklearn.cluster import KMeans
from sklearn import metrics
from sklearn import preprocessing


#normalizing
min_max_scaler = preprocessing.MinMaxScaler()
feats_norm = min_max_scaler.fit_transform(feats1)

# Clustering with k=2..20
silhouette=[]
for k in range(2,21):
  # Number of clusters
  kmeans = KMeans(n_clusters=k)
  # Fitting the input data
  kmeans = kmeans.fit(feats1)
  # Getting the cluster labels
  labels = kmeans.predict(feats1)
  # Centroid values
  centroids = kmeans.cluster_centers_
  # Save silhouette score
  silhouette.append((k, metrics.silhouette_score(feats1, labels, metric='euclidean'), labels))

# Determining the maximum 
best_k=0
best_metric=0
best_labels=[]
for i in range(len(silhouette)):
  if silhouette[i][1] > best_metric:
    best_k, best_metric, best_labels = silhouette[i]
y = best_labels

Para evaluar la calidad de los grupos encontrados utilizamos una medida de cohesión vista en clases WSS y otra que combina la cohesión y separación (Silhoette).

In [0]:
import matplotlib.pyplot as plt

plt.plot([m[0]for m in silhouette], [m[1]for m in silhouette], 'bo-', linewidth=2)
# plt.axis([0, 20, 0, 1])
plt.show()

Clasificación

Deducimos del análisis anterior que los mejores valores se obtienen con dos y tres grupos. Luego precedimos a utilizar estos grupos como etiquetas para la clasificación.

In [0]:
from sklearn.model_selection import cross_validate, cross_val_predict, train_test_split
from sklearn.metrics import f1_score, recall_score, precision_score, accuracy_score, classification_report


def run_classifier(clf, X, y, num_tests=100):
    metrics = {'f1-score': [], 'precision': [], 'recall': []}
    
    for _ in range(num_tests):
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, stratify=y)
        clf.fit(X_train, y_train)    ## Entrenamos con X_train y clases y_train
        predictions = clf.predict(X_test)   ## Predecimos con nuevos datos (los de test X_test) 
        
        metrics['f1-score'].append(f1_score(y_test, predictions, average='micro'))  # X_test y y_test deben ser definidos previamente
        metrics['recall'].append(recall_score(y_test, predictions, average='micro'))
        metrics['precision'].append(precision_score(y_test, predictions, average='micro'))
    
    return metrics
  
def run_cross_validation(clf, X, y, cv=7):
  predictions = cross_val_predict(clf, X, y, cv=cv)

  print("Metrics:")
  print(classification_report(y, predictions))
  print("f1_score:", f1_score(y, predictions, average='micro'))  # X_test y y_test deben ser definidos previamente
  print("recall_score:", recall_score(y, predictions, average='micro'))
  print("precision_score:", precision_score(y, predictions, average='micro'))
  print("Accuracy:", accuracy_score(y, predictions))
  
  
def run_linear_regression(clf, X, y):
  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, stratify=y)
  
  regr.fit(X_train, y_train)

  y_pred = regr.predict(X_test)

  print('Coefficients: \n', regr.coef_)
  print("Mean squared error: %.2f" % math.sqrt(mean_squared_error(y_test, y_pred)))
  print('Variance score: %.2f' % r2_score(y_test, y_pred))
In [33]:
from sklearn.dummy import DummyClassifier
from sklearn.svm import SVC, NuSVC, LinearSVC  # support vector machine classifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB  # naive bayes
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

c0 = ("Base Dummy", DummyClassifier(strategy='stratified'))
c1 = ("Decision Tree", DecisionTreeClassifier())
c2 = ("Gaussian Naive Bayes", GaussianNB())
c3 = ("KNN", KNeighborsClassifier(n_neighbors=25))
c4 = ("SVM-SVC", SVC(gamma=.001))
c5 = ("SVM-NuSVC", NuSVC(gamma=.001))
c6 = ("SVM-LinearSVC", LinearSVC(random_state=0, tol=1e-5))

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='scale', kernel='rbf',
    max_iter=-1, probability=False, random_state=None, shrinking=True,
    tol=0.001, verbose=False)

NuSVC(cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape='ovr', degree=3, gamma='scale', kernel='rbf',
      max_iter=-1, nu=0.5, probability=False, random_state=None,
      shrinking=True, tol=0.001, verbose=False)

LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='squared_hinge', max_iter=1000,
     multi_class='ovr', penalty='l2', random_state=0, tol=1e-05, verbose=0)


classifiers = [c0, c1, c2, c3, c4, c6]

for name, clf in classifiers:
    print("----------------")
    print("Resultados para clasificador: ",name) 
    
    print("\nRandom partitions:")
    metrics = run_classifier(clf, X, y)
    print("Precision promedio:",np.array(metrics['precision']).mean())
    print("Recall promedio:",np.array(metrics['recall']).mean())
    print("F1-score promedio:",np.array(metrics['f1-score']).mean())
    
    print("\nCross-validation:")
    run_cross_validation(clf, X, y)
    
    print("----------------\n\n")
    
----------------
Resultados para clasificador:  Base Dummy

Random partitions:
Precision promedio: 0.8352238805970148
Recall promedio: 0.8352238805970148
F1-score promedio: 0.8352238805970148

Cross-validation:
Metrics:
              precision    recall  f1-score   support

           0       0.90      0.88      0.89       201
           1       0.04      0.05      0.04        20

   micro avg       0.80      0.80      0.80       221
   macro avg       0.47      0.46      0.47       221
weighted avg       0.82      0.80      0.81       221

f1_score: 0.8009049773755657
recall_score: 0.8009049773755657
precision_score: 0.8009049773755657
Accuracy: 0.8009049773755657
----------------


----------------
Resultados para clasificador:  Decision Tree

Random partitions:
Precision promedio: 0.8423880597014926
Recall promedio: 0.8423880597014926
F1-score promedio: 0.8423880597014926

Cross-validation:
Metrics:
              precision    recall  f1-score   support

           0       0.92      0.92      0.92       201
           1       0.20      0.20      0.20        20

   micro avg       0.86      0.86      0.86       221
   macro avg       0.56      0.56      0.56       221
weighted avg       0.86      0.86      0.86       221

f1_score: 0.8552036199095022
recall_score: 0.8552036199095022
precision_score: 0.8552036199095022
Accuracy: 0.8552036199095022
----------------


----------------
Resultados para clasificador:  Gaussian Naive Bayes

Random partitions:
Precision promedio: 0.4749253731343284
Recall promedio: 0.4749253731343284
F1-score promedio: 0.4749253731343284

Cross-validation:
Metrics:
              precision    recall  f1-score   support

           0       0.89      0.53      0.67       201
           1       0.07      0.35      0.12        20

   micro avg       0.52      0.52      0.52       221
   macro avg       0.48      0.44      0.39       221
weighted avg       0.82      0.52      0.62       221

f1_score: 0.5158371040723982
recall_score: 0.5158371040723982
precision_score: 0.5158371040723982
Accuracy: 0.5158371040723982
----------------


----------------
Resultados para clasificador:  KNN

Random partitions:
Precision promedio: 0.9104477611940301
Recall promedio: 0.9104477611940301
F1-score promedio: 0.9104477611940301

Cross-validation:
Metrics:
              precision    recall  f1-score   support

           0       0.91      1.00      0.95       201
           1       0.00      0.00      0.00        20

   micro avg       0.91      0.91      0.91       221
   macro avg       0.45      0.50      0.48       221
weighted avg       0.83      0.91      0.87       221

f1_score: 0.9095022624434389
recall_score: 0.9095022624434389
precision_score: 0.9095022624434389
Accuracy: 0.9095022624434389
----------------


----------------
Resultados para clasificador:  SVM-SVC

Random partitions:
/usr/local/lib/python3.6/dist-packages/sklearn/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
Precision promedio: 0.9104477611940301
Recall promedio: 0.9104477611940301
F1-score promedio: 0.9104477611940301

Cross-validation:
Metrics:
              precision    recall  f1-score   support

           0       0.91      1.00      0.95       201
           1       0.00      0.00      0.00        20

   micro avg       0.91      0.91      0.91       221
   macro avg       0.45      0.50      0.48       221
weighted avg       0.83      0.91      0.87       221

f1_score: 0.9095022624434389
recall_score: 0.9095022624434389
precision_score: 0.9095022624434389
Accuracy: 0.9095022624434389
----------------


----------------
Resultados para clasificador:  SVM-LinearSVC

Random partitions:
/usr/local/lib/python3.6/dist-packages/sklearn/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
Precision promedio: 0.9104477611940301
Recall promedio: 0.9104477611940301
F1-score promedio: 0.9104477611940301

Cross-validation:
Metrics:
              precision    recall  f1-score   support

           0       0.91      1.00      0.95       201
           1       0.00      0.00      0.00        20

   micro avg       0.91      0.91      0.91       221
   macro avg       0.45      0.50      0.48       221
weighted avg       0.83      0.91      0.87       221

f1_score: 0.9095022624434389
recall_score: 0.9095022624434389
precision_score: 0.9095022624434389
Accuracy: 0.9095022624434389
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/metrics/classification.py:1143: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)

Regresión Lineal

A su vez, para predecir el tiempo que se mantendrán invariante los resultados de una consulta (ttl), decidimos modelarlo como la estimación de un valor continuo. Para esto entrenamos un modelo regresión lineal con la librería sklearn y tensorflow. Esta última nos permite ver cómo se va comportando la estimación por períodos (mediante el método de descenso del gradiente) y analizar la convergencia. Ambas arrojaron los mismos resultados.

In [0]:
from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score

# Create linear regression object
regr = linear_model.LinearRegression()

run_linear_regression(regr, X, y)
Coefficients: 
 [ 1.03858967 -0.47972253  0.18675496  0.04060257 -0.00913857 -0.08907127
 -0.02082791 -0.09633581 -0.32946964 -0.27913595 -0.03573651]
Mean squared error: 0.66
Variance score: 0.00
In [0]:
import math

from IPython import display
from matplotlib import cm
from matplotlib import gridspec
from matplotlib import pyplot as plt
from sklearn import metrics
import tensorflow as tf
from tensorflow.python.data import Dataset

tf.logging.set_verbosity(tf.logging.ERROR)
pd.options.display.max_rows = 10
pd.options.display.float_format = '{:.1f}'.format

def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):
  # Convertir datos pandas en un dict de arrays np.
  features = {key:np.array(value) for key,value in dict(features).items()}                                           

  # Construir un dataset, y configure batching/repeating.
  ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
  ds = ds.batch(batch_size).repeat(num_epochs)

  # Mezclar los datos, si se especifica.
  if shuffle:
    ds = ds.shuffle(buffer_size=10000)

  # Devolver el nuevo lote de datos.
  features, labels = ds.make_one_shot_iterator().get_next()
  return features, labels

def train_model(X, y, learning_rate, steps, batch_size, features_names):
  periods = 2
  steps_per_period = steps / periods

  #feature_data = california_housing_dataframe[[input_feature]]
  #feature_target = "median_house_value"
  #targets = california_housing_dataframe[feature_target]

  # Crear columns característica.
  feature_columns = [tf.feature_column.numeric_column(e) for e in features_names]
  
  # Crear funciones input.
  training_input_fn = lambda:my_input_fn(X, y, batch_size=batch_size)
  prediction_input_fn = lambda:my_input_fn(X, y, num_epochs=1, shuffle=False)
  
  # Crear un objeto linear regressor.
  my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
  #my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
  linear_regressor = tf.estimator.LinearRegressor(
      feature_columns=feature_columns,
      optimizer=my_optimizer
  )

  # Configuración para trazar el estado de la línea de nuestro modelo cada período.
  #plt.figure(figsize=(15, 6))
  #plt.subplot(1, 2, 1)
  #plt.title("Línea aprendida por Período")
  #plt.ylabel(feature_target)
  #plt.xlabel(input_feature)
  #sample = california_housing_dataframe.sample(n=300)
  #plt.scatter(sample[input_feature], sample[feature_target])
  #colors = [cm.coolwarm(x) for x in np.linspace(-1, 1, periods)]

  # Entrena el modelo, pero haciéndolo dentro de un loop de modo que podamos periódicamente
  # evaluar las métricas de pérdida.
  print("Entrenamiento del modelo...")
  print("RMSE (en datos de entrenamiento):")
  root_mean_squared_errors = []
  for period in range (0, periods):
    # Entrenar el modelo, empezando desde el estado anterior.
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period
    )
    
    # Calcular predicciones.
    predictions = linear_regressor.predict(input_fn=prediction_input_fn)
    predictions = np.array([item['predictions'][0] for item in predictions])
    
    # Calcular pérdida.
    root_mean_squared_error = math.sqrt(metrics.mean_squared_error(predictions, y))
    
    # Ocasionalmente imprimir la pérdida actual.
    print("  período %02d : %0.2f" % (period, root_mean_squared_error))
    
    # Agregar las métricas de pérdida de este período a nuestra lista.
    root_mean_squared_errors.append(root_mean_squared_error)
    # Por último, rastrea los pesos y los sesgos a lo largo del tiempo.
    # Aplica algo de math para asegurarte que los datos y la línea se representan claramente.
    #y_extents = np.array([0, sample[feature_target].max()])
    
    #weight = linear_regressor.get_variable_value('linear/linear_model/%s/weights' % input_feature)[0]
    #bias = linear_regressor.get_variable_value('linear/linear_model/bias_weights')

    #x_extents = (y_extents - bias) / weight
    #x_extents = np.maximum(np.minimum(x_extents, sample[input_feature].max()), sample[input_feature].min())
    #y_extents = weight * x_extents + bias
    #plt.plot(x_extents, y_extents, color=colors[period]) 
  print("Entrenamiento del Modelo finalizado.")

  # Muestra un gráfico de métricas de pérdida por períodos.
  #plt.subplot(1, 2, 2)
  #plt.ylabel('RMSE')
  #plt.xlabel('Períodos')
  #plt.title("Raíz Error Cuádratico Medio vs. Períodos")
  #plt.tight_layout()
  #plt.plot(root_mean_squared_errors)

  # Muestra una tabla con los datos de calibración.
  calibration_data = pd.DataFrame()
  calibration_data['predictions'] = pd.Series(predictions)
  calibration_data['targets'] = pd.Series(y['ttl'])
  display.display(calibration_data.describe())

  print("RMSE final(en datos de entrenamiento): %0.2f" % root_mean_squared_error)
  return linear_regressor
In [0]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, stratify=y)

X_train = pd.DataFrame(X_train, columns=[str(e) for e in range(1, 12)])
X_test = pd.DataFrame(X_test, columns=[str(e) for e in range(1, 12)])
y_train = pd.DataFrame(y_train)
y_test = pd.DataFrame(y_test)

linear_regressor = train_model(
    X=X_train,
    y=y_train,
    learning_rate=0.00002,
    steps=20000,
    batch_size=50,
    features_names=[str(e) for e in range(1, 12)]
)
Entrenamiento del modelo...
RMSE (en datos de entrenamiento):
  período 00 : 0.74
  período 01 : 0.74
Entrenamiento del Modelo finalizado.
predictions targets
count 1121.0 791.0
mean 1.2 1.2
std 0.1 0.7
min 1.0 1.0
25% 1.2 1.0
50% 1.3 1.0
75% 1.3 1.0
max 1.4 8.0
RMSE final(en datos de entrenamiento): 0.74
In [0]:
print('Test del modelo...')

# Calcular predicciones.
predictions = linear_regressor.predict(input_fn=lambda:my_input_fn(X_test, y_test, num_epochs=1, shuffle=False))
predictions = np.array([item['predictions'][0] for item in predictions])
    
# Calcular pérdida.
root_mean_squared_error = math.sqrt(metrics.mean_squared_error(predictions, y_test))
    
# Ocasionalmente imprimir la pérdida actual.
print("RMSE (en datos de test): %0.2f" % (root_mean_squared_error))
Test del modelo...
RMSE (en datos de test): 0.66

Los resultados anteriores no son cloncluyentes, pero sugieren que es posible predecir los cambios futuros en los resultados de las consultas usando regresión lineal.

Conclusiones y direcciones futuras

Los resultados obtenidos en los experimentos muestran que de manera general la calidad obtenida en la clasificación por los distintos métodos no fue buena, incluso utilizando las etiquetas devueltas por el agrupamiento. Teniendo en cuenta esto, no podemos demostrar el cumplimiento de la hipótesis inicial, pero consideramos que pueden mejorar los resultados alcanzados si tomamos una muestra de consultas más representativa y aumentar la ventana de tiempo de monitoreo de las mismas y del dataset.

Para ampliar el dataset de consultas Actualmente nuestro dataset de consultas no es representativo, cuenta con solo 221 consultas que solo usan 153 predicados de los 3360 posibles. En este sentido, estamos trabajando en replicar todo este análisis sobre el dataset Wikidata SPARQL Logs y generar nuevas consultas automáticamente en lenguaje SPARQL.

Tambien consideraremos agregar características de la semántica de los operadores en las consultas y otras características de la dinamicidad de los datos.

Es válido señalar que se realizaron cambios teniendo en cuenta las observaciones realizadas en el hito 2, tales como: concretar la hipótesis general y las preguntas de investigación a contestar; se realizaron experimentos para determinar si las consultas que se tenían eran representativas.

Referencias

[Cho and Garcia-Molina, 2000] Junghoo Cho and Hector Garcia-Molina. The evolution of the web and implications for an incremental crawler. In VLDB, pages 200–209, 2000.

[Haslhofer y Popitsch, 2009] B. Haslhofer and N. Popitsch. DSNnotify - detecting and fixing broken links in linked data sets. In DEXA ’09 Workshop on Web Semantics (WebS ’09), 2009.

[Pandey et al., 2003] Sandeep Pandey, Krithi Ramamritham, and Soumen Chakrabarti. Monitoring the dynamic web to respond to continuous queries. In World Wide Web Conference (WWW ’03), pages 659–668, 2003.